package frontend.pages.worklog.daily;

import backend.model.admin.*;
import backend.model.other.MonthState;
import backend.model.other.User;
import backend.model.worklog.WorkLog;
import backend.service.admin.ProjectService;
import backend.service.admin.SfiService;
import backend.service.admin.ShiftService;
import backend.service.admin.WorkplaceService;
import backend.service.charisma.CharismaDataSynchronizer;
import backend.service.other.MonthStateService;
import backend.service.other.UserService;
import com.google.common.collect.Lists;
import frontend.core.TimeTrackingWebApplication;
import frontend.core.TimeTrackingWebSession;
import frontend.model.worklog.WorkLogUIModel;
import frontend.pages.admin.base.AbstractManagingForm;
import frontend.pages.reports.charismasynch.FutureUpdateBehavior;
import frontend.components.base.ComponentFactory;
import frontend.components.other.DBModelChoiceRenderer;
import frontend.components.other.UserInputCallableAction;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Localizer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.request.resource.ContextRelativeResource;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.validation.ValidationError;
import shared.exceptions.CharismaConnectionDownException;
import shared.other.MonthStateEnum;
import shared.other.UserRolesDefinition;

import java.util.*;
import java.util.concurrent.*;

public class DailyWorkLogForm extends AbstractManagingForm<WorkLogUIModel> {

    public static final String RESOURCE_KEY_FIELD_EMPLOYEE = "pages.dailyWorkLogPage.form.employee";
    public static final String RESOURCE_KEY_FIELD_LABEL_PROJECT = "pages.dailyWorkLogPage.form.project";
    public static final String RESOURCE_KEY_FIELD_LABEL_SFI = "pages.dailyWorkLogPage.form.sfi";
    public static final String RESOURCE_KEY_FIELD_LABEL_WORKPLACE = "pages.dailyWorkLogPage.form.workplace";
    public static final String RESOURCE_KEY_FIELD_LABEL_WORK_DETAIL = "pages.dailyWorkLogPage.form.workDetail";
    public static final String RESOURCE_KEY_FIELD_LABEL_SHIFT = "pages.dailyWorkLogPage.form.shift";
    public static final String RESOURCE_KEY_FIELD_LABEL_NORMAL_HOURS = "pages.dailyWorkLogPage.form.hours";
    public static final String RESOURCE_KEY_VALIDATION_PROJECT_SFI_WORKPLACE_ALREADY_EXISTS = "validation.form.dailyWorkLog.projectSfiWorkplaceAlreadyExists";
    public static final String RESOURCE_KEY_VALIDATION_MAX_NUMBER_OF_NORMAL_HOURS_EXCEEDED = "validation.form.dailyWorkLog.maxNoOfHoursExceeded";
    public static final String RESOURCE_KEY_VALIDATION_INVALID_NO_OF_HOURS= "validation.form.dailyWorkLog.invalidNoOfHours";
    public static final String RESOURCE_KEY_VALIDATION_CLOSED_OR_BLOCKED_MONTH = "validation.form.dailyWorkLog.closedOrBlockedMonth";
    public static final String RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_IMPORT = "validation.form.dailyWorkLog.connectionDownCannotImport";
    public static final String RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_VALIDATE = "validation.form.dailyWorkLog.connectionDownCannotValidate";
    public static final String IMPORT_HOURS_LINK_IMAGE = "images/icon_set/16/Database.png";
    public static final String IMPORT_HOURS_LINK_TEMP_IMAGE = "images/preloader_transparent.gif";
    public static final int MAX_NUMBER_OF_HOURS = 24;

    private List<WorkLog> workLogList = new ArrayList<WorkLog>();
    private boolean showDeleteButton;
    private UserInputCallableAction onSaveAction;
    private Panel workDetailComponent;
    private CollectionModel workDetailsCollectionModel;
    private Panel sfiComponent;
    private Panel workplaceComponent;
    private Panel shiftComponent;
    private TextField<String> hoursComponent;
    private AjaxLink importFromCharismaLink;


    public DailyWorkLogForm(String id, WorkLogUIModel model, List<WorkLog> workLogList, boolean showDeleteButton, UserInputCallableAction onSaveAction) {
        super(id,  model);
        this.showDeleteButton = showDeleteButton;
        this.workLogList = workLogList;
        this.onSaveAction = onSaveAction;
    }

    @Override
    protected void constructFormContent() {
        Panel projectComponent = ComponentFactory.createDropDownChoiceComponent(
                "project", RESOURCE_KEY_FIELD_LABEL_PROJECT, uiModel, getAllActiveProjects(), new DBModelChoiceRenderer<Project>(), false, getOnProjectSelectAction());
        sfiComponent = ComponentFactory.createDropDownChoiceComponent(
                "sfi", RESOURCE_KEY_FIELD_LABEL_SFI, uiModel, getAllActiveSfis(), new DBModelChoiceRenderer<Sfi>(), false);
        workplaceComponent = ComponentFactory.createDropDownChoiceComponent("workplace", RESOURCE_KEY_FIELD_LABEL_WORKPLACE, uiModel,
                new CollectionModel(getAllActiveWorkplaces()), new DBModelChoiceRenderer<Workplace>(), false, getOnWorkplaceSelectAction());
        workDetailsCollectionModel = new CollectionModel(getWorkDetailsForWorkplace());
        workDetailComponent = ComponentFactory.createDropDownChoiceComponent("workDetail", RESOURCE_KEY_FIELD_LABEL_WORK_DETAIL, uiModel,
                workDetailsCollectionModel, new DBModelChoiceRenderer<WorkDetail>(), true, null);
        shiftComponent = ComponentFactory.createDropDownChoiceComponent(
                "shift", RESOURCE_KEY_FIELD_LABEL_SHIFT, uiModel, getAllActiveShifts(), new DBModelChoiceRenderer<Shift>(), false);

        add(projectComponent, sfiComponent, workplaceComponent, workDetailComponent, shiftComponent);
        addTextFieldAndImportHoursButton();

        if (uiModel.getProject() != null) {
            updateFormComponentsEnabledProperty(uiModel.getProject().getIsDirectPayment());
        }
    }

    private List<WorkDetail> getWorkDetailsForWorkplace() {
        List<WorkDetail> workDetails = new ArrayList<WorkDetail>();
        if (uiModel.getWorkplace() != null) {
            workDetails = Lists.newArrayList(uiModel.getWorkplace().getWorkDetails());
            Collections.sort(workDetails);
        }
        return workDetails;
    }

    private UserInputCallableAction<Project> getOnProjectSelectAction() {
        return new UserInputCallableAction<Project>() {
            @Override
            public void call(AjaxRequestTarget target, Project project) {
                if (project != null) {
                    boolean isDirectPayment = project.getIsDirectPayment();
                    updateFormComponentsEnabledProperty(isDirectPayment);
                    target.add(sfiComponent, shiftComponent, workplaceComponent, workDetailComponent, hoursComponent, importFromCharismaLink);
                }
            }
        };
    }

    private void updateFormComponentsEnabledProperty(boolean directPayment) {
        sfiComponent.setEnabled(directPayment);
        shiftComponent.setEnabled(directPayment);
        workplaceComponent.setEnabled(directPayment);
        workDetailComponent.setEnabled(directPayment);
        hoursComponent.setEnabled(directPayment);
        importFromCharismaLink.setEnabled(directPayment);
    }

    public UserInputCallableAction<Workplace> getOnWorkplaceSelectAction() {
        return new UserInputCallableAction<Workplace>() {
            @Override
            public void call(AjaxRequestTarget target, Workplace workplace) {
                List<WorkDetail> workDetails = new ArrayList<WorkDetail>();
                if (workplace != null && workplace.getWorkDetails() != null) {
                    workDetails = Lists.newArrayList(workplace.getWorkDetails());
                    Collections.sort(workDetails);
                }
                workDetailsCollectionModel.setObject(workDetails);
                target.add(feedbackPanel);
                target.add(workDetailComponent);
            }
        };
    }

    private void addTextFieldAndImportHoursButton() {
        Label label = new Label("hoursLabel", new ResourceModel(RESOURCE_KEY_FIELD_LABEL_NORMAL_HOURS));
        hoursComponent = new TextField<String>("hoursTextField", new PropertyModel<String>(uiModel, "hours"));
        hoursComponent.setOutputMarkupId(true);
        final Image linkImg = new Image("linkImg", new ContextRelativeResource(IMPORT_HOURS_LINK_IMAGE));

        importFromCharismaLink = new AjaxLink("importHoursLink") {
            @Override
            public void onClick(final AjaxRequestTarget target) {
                importHoursFromCharisma(target, uiModel, linkImg);
            }
        };
        importFromCharismaLink.add(linkImg);
        importFromCharismaLink.add(AttributeModifier.replace("title", new ResourceModel(RESOURCE_KEY_FIELD_LABEL_NORMAL_HOURS + ".tooltip")));
        importFromCharismaLink.setOutputMarkupId(true);

        add(label, hoursComponent, importFromCharismaLink);
        setOutputMarkupId(true);
        setOutputMarkupPlaceholderTag(true);
    }

    private void importHoursFromCharisma(final AjaxRequestTarget target, final WorkLogUIModel uiModel, final Image linkImg) {
        final Calendar selectedDay = TimeTrackingWebSession.getSession().getSelectedDay();
        final ExecutorService executor = Executors.newFixedThreadPool(1);


        Future futureTask = executor.submit(new Callable() {
            @Override
            public Integer call() throws Exception {
                int importedHours = Integer.valueOf(uiModel.getHours());
                try {
                    CharismaDataSynchronizer charismaDataSynchronizer = TimeTrackingWebApplication.getBean(CharismaDataSynchronizer.class);
                    importedHours = charismaDataSynchronizer.getCharismaHoursForDateAndEmployee(selectedDay.getTime(), uiModel.getEmployee());
                } catch (CharismaConnectionDownException ccde) {
                    ValidationError validationError = createValidationError(RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_IMPORT);
                    error(validationError.getMessage());
                    target.add(feedbackPanel);
                }
                uiModel.setHours(importedHours);
                return importedHours;
            }
        });

        CharismaDataSynchronizer charismaDataSynchronizer = TimeTrackingWebApplication.getBean(CharismaDataSynchronizer.class);
        charismaDataSynchronizer.setCharismaHoursSynchTask(futureTask);


        FutureUpdateBehavior<Void> futureUpdateBehavior = new FutureUpdateBehavior<Void>(Duration.seconds(1), futureTask) {
            @Override
            protected void onPostSuccess(AjaxRequestTarget target) {
                try {
                    CharismaDataSynchronizer charismaDataSynchronizer = TimeTrackingWebApplication.getBean(CharismaDataSynchronizer.class);
                    Future futureTask = charismaDataSynchronizer.getCharismaHoursSynchTask();
                    Integer importedHours = (Integer) futureTask.get();
                    uiModel.setHours(importedHours.intValue());
                } catch (Exception e) {
                    ValidationError validationError = createValidationError(RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_IMPORT);
                    error(validationError.getMessage());
                    target.add(feedbackPanel);
                } finally {
                    linkImg.setImageResource(new ContextRelativeResource(IMPORT_HOURS_LINK_IMAGE));
                    target.add(DailyWorkLogForm.this);
                }
            }

            @Override
            protected void onUpdateError(AjaxRequestTarget target, Exception e) {
                ValidationError validationError = createValidationError(RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_IMPORT);
                error(validationError.getMessage());
                linkImg.setImageResource(new ContextRelativeResource(IMPORT_HOURS_LINK_IMAGE));
                target.add(DailyWorkLogForm.this);
            }
        };

        add(futureUpdateBehavior);
        linkImg.setImageResource(new ContextRelativeResource(IMPORT_HOURS_LINK_TEMP_IMAGE));
        target.add(this);
    }

    @Override
    protected void postSave(AjaxRequestTarget target) {
        try {
            updateWorkLogMismatches();
        } catch (CharismaConnectionDownException ccde) {
            String warnMessage = Localizer.get().getString(RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_VALIDATE, null);
            warn(warnMessage);
        }
        String infoMessage = Localizer.get().getString(RESOURCE_KEY_INFO_ENTITY_SAVED_SUCCESSFULLY, null);
        info(infoMessage);
        target.add(feedbackPanel);
        onSaveAction.call(target, infoMessage);
    }

    @Override
    protected void postDelete(AjaxRequestTarget target) {
        try {
            updateWorkLogMismatches();
        } catch (CharismaConnectionDownException ccde) {
            String warnMessage = Localizer.get().getString(RESOURCE_KEY_VALIDATION_CONNECTION_DOWN_CANNOT_VALIDATE, null);
            warn(warnMessage);
        }
        String infoMessage = Localizer.get().getString(RESOURCE_KEY_INFO_ENTITY_DELETED_SUCCESSFULLY, null);
        info(infoMessage);
        onSaveAction.call(target, infoMessage);
    }

    private void updateWorkLogMismatches() throws CharismaConnectionDownException{
        CharismaDataSynchronizer synchronizer = TimeTrackingWebApplication.getBean(CharismaDataSynchronizer.class);
        synchronizer.validateHoursForEmployeeAndDate(uiModel.getEmployee(), TimeTrackingWebSession.getSession().getSelectedDay().getTime());
    }

    private List<Project> getAllActiveProjects() {
        List<Project> projects = new ArrayList<Project>();
        projects.addAll(TimeTrackingWebApplication.getService(ProjectService.class).findAllActiveProjects());
        Collections.sort(projects);
        return projects;
    }

    private List<Sfi> getAllActiveSfis() {
        List<Sfi> sfis = new ArrayList<Sfi>();
        sfis.addAll(TimeTrackingWebApplication.getService(SfiService.class).findAllActiveSfis());
        Collections.sort(sfis);
        return sfis;
    }

    private List<Workplace> getAllActiveWorkplaces() {
        List<Workplace> workplaces = new ArrayList<Workplace>();
        workplaces.addAll(TimeTrackingWebApplication.getService(WorkplaceService.class).findAllActiveWorkplaces());
        Collections.sort(workplaces);
        return workplaces;
    }

    private List<Shift> getAllActiveShifts() {
        List<Shift> shifts = new ArrayList<Shift>();
        shifts.addAll(TimeTrackingWebApplication.getService(ShiftService.class).findAllActiveShifts());
        Collections.sort(shifts);
        return shifts;
    }

    @Override
    protected Class getResponsePage() {
        return DailyWorkLogPage.class;
    }

    @Override
    protected void doFormValidation() {
        if (!validateNotEmpty(uiModel.getEmployee(), RESOURCE_KEY_FIELD_EMPLOYEE)) {
            return;
        }

        boolean projectNotEmpty = validateNotEmpty(uiModel.getProject(), RESOURCE_KEY_FIELD_LABEL_PROJECT);
        if (projectNotEmpty && uiModel.getProject().getIsDirectPayment()) {
            boolean sfiNotEmpty = validateNotEmpty(uiModel.getSfi(), RESOURCE_KEY_FIELD_LABEL_SFI);
            boolean workplaceNotEmpty = validateNotEmpty(uiModel.getWorkplace(), RESOURCE_KEY_FIELD_LABEL_WORKPLACE);
            if (sfiNotEmpty && workplaceNotEmpty) {
                validateProjectSfiWorkplaceUnique();
            }
            boolean shiftNotEmpty = validateNotEmpty(uiModel.getShift(), RESOURCE_KEY_FIELD_LABEL_SHIFT);
            validateHoursNotZero();
            validateMaxNumberOfHoursNotReached();
        }

        validateMonthIsNotBlockedOrClosed();
    }

    private void validateHoursNotZero() {
        Integer hours = 0;
        try {
            hours = Integer.valueOf(uiModel.getHours());
        } catch (NumberFormatException nfe) {
            // default to 0
        }
        if (hours <= 0) {
            addFieldValidationError(RESOURCE_KEY_FIELD_LABEL_NORMAL_HOURS, RESOURCE_KEY_VALIDATION_INVALID_NO_OF_HOURS);
        }
    }

    private void validateMaxNumberOfHoursNotReached() {
        int alreadyBookedNumberOfHours = 0;
        for (WorkLog workLog : workLogList) {
            if (workLog.getId() != uiModel.getDBModel().getId()) {
                alreadyBookedNumberOfHours += workLog.getHours();
            }
        }

        int hours = 0;
        try {
            hours = Integer.valueOf(uiModel.getHours());
        } catch (NumberFormatException nfe) {
            // default to 0
        }
        int totalHours = alreadyBookedNumberOfHours + hours;
        if (totalHours > MAX_NUMBER_OF_HOURS) {
            addFieldValidationError(RESOURCE_KEY_FIELD_LABEL_NORMAL_HOURS, RESOURCE_KEY_VALIDATION_MAX_NUMBER_OF_NORMAL_HOURS_EXCEEDED);
        }
    }

    private void validateProjectSfiWorkplaceUnique() {
        Project project = uiModel.getProject();
        Sfi sfi = uiModel.getSfi();
        Workplace workplace = uiModel.getWorkplace();
        for (WorkLog workLog : workLogList) {
            if (workLog.getProject().getId() == project.getId()
                    && workLog.getWorkplace().getId() == workplace.getId()
                    && workLog.getSfi().getId() == sfi.getId()
                    && (workLog.getId() != uiModel.getDBModel().getId())) {
                addValidationError(RESOURCE_KEY_VALIDATION_PROJECT_SFI_WORKPLACE_ALREADY_EXISTS);
                return;
            }
        }
    }

    private void validateMonthIsNotBlockedOrClosed() {
        MonthStateService monthStateService = TimeTrackingWebApplication.getService(MonthStateService.class);
        Date date = TimeTrackingWebSession.getSession().getSelectedDay().getTime();
        MonthState monthState = monthStateService.findMonthStateByDate(date);
        if (monthState != null) {
            String loggedInUsername = (String) TimeTrackingWebSession.get().getAttribute(TimeTrackingWebSession.LOGIN_USERNAME_ATTR);
            User loggedInUser = TimeTrackingWebApplication.getService(UserService.class).findUserByUsername(loggedInUsername);
            UserRolesDefinition.UserRole userRole = loggedInUser.getRole();
            if (monthState.getState() == MonthStateEnum.BLOCKED && !userRole.equals(UserRolesDefinition.UserRole.ADMIN)
                    || monthState.getState() == MonthStateEnum.CLOSED) {
                addValidationError(RESOURCE_KEY_VALIDATION_CLOSED_OR_BLOCKED_MONTH);
            }
        }
    }

    protected boolean isDeleteButtonVisible() {
        return showDeleteButton;
    }
}
